Rememeber Redux and MobX? Well guess what, there are even MORE choices to pick from when it comes to implementing global state within React applications!
Global state management.
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers/rootReducer";
export default function configureStore() {
return createStore(rootReducer, applyMiddleware(thunk));
}
//src/reducers/testReducer.js
export default(state = {}, action => {
switch(action.type) {
case 'THE_TEST':
return {
...state,
test: action.payload
}
default:
return state
}
})
//src/reducers/rootReducer.js
import { combineReducers } from 'redux'
import testReducer from './testReducer'
export default combineReducers({
testReducer
});
import { THE_TEST } from "./constants";
export const theTest = (payload) => {
return {
type: THE_TEST,
payload,
};
};
export const getTest = () => {
return function (dispatch) {
dispatch(theTest());
};
};
import { getTest } from "../actions/testAction";
import { connect } from "react-redux";
const mapStateToProps = (state) => ({
...state,
});
const mapDispatchToProps = (dispatch) => ({
getTest: () => getTest(testAction()),
});
class testComponent extends Component {
simpleTest = (event) => {
this.props.getTest();
};
render() {
return <button onClick={simpleTest}> Test </button>;
}
}
export default connect(mapStateToProps, mapDispatchToProps)(testComponent);
A wrapper around Redux.
npm install @reduxjs/toolkit react-redux
You can create a boiler plate react app with redux
npx create-react-app app-name --template redux
import { Provider } from "react-redux";
import { store } from "./app/store";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
value: 0,
};
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./slices/counterSlice";
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement } from "./app/slices/counterSlice";
function App() {
const dispatch = useDispatch();
const count = useSelector((state) => state.counter.value);
return (
<div className="App">
<div>{count}</div>
<div>
<button onClick={() => dispatch(decrement())}>- 1</button>
<button onClick={() => dispatch(increment())}>+ 1</button>
</div>
</div>
);
}
Proxy state.
import "./styles.css";
import React, { useEffect, useRef, useState } from "react";
import { proxy, useSnapshot } from "valtio";
type Todo = {
id: number,
title: string,
completed: boolean,
};
const state = proxy < { todo: Todo | null } > { todo: null };
const setTodo = (todo: Todo) => {
state.todo = todo;
};
const updateTodoTitle = (title: string) => {
if (state.todo !== null) {
state.todo.title = title;
}
};
const updateCompleted = () => {
if (state.todo !== null) {
state.todo.completed = !state.todo.completed;
}
};
const App = React.memo(() => {
const snap = useSnapshot(state);
const [title, setTitle] = useState("");
const rerenderCountRef = useRef(0);
const handleDataFetching = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const todo = await res.json();
setTodo(todo);
};
useEffect(() => {
rerenderCountRef.current += 1;
});
return (
<div className="App">
<div style={{ marginBottom: 10 }}>
<b>Render count:</b> {rerenderCountRef.current}
</div>
<button style={{ marginBottom: 10 }} onClick={handleDataFetching}>
Fetch data
</button>
{snap.todo !== null ? (
<div>
<p>
<b>Todo title from valtio:</b> {snap.todo.title}
</p>
<form
onSubmit={(e) => {
e.preventDefault();
updateTodoTitle(title);
}}
style={{ marginBottom: 10 }}
>
<label>
New title to use in valtio{" "}
<input
value={title}
onChange={(e) => {
setTitle(e.target.value);
}}
/>
</label>
<button>Save to global store</button>
</form>
<button type="button" onClick={updateCompleted}>
Toggle todo's completed value
</button>
</div>
) : (
<div>Store is empty</div>
)}
</div>
);
});
export default App;
Caching/fetching data.
import { createApi, fetchBaseQuery } from "@rtk-incubator/rtk-query";
// Create your service using a base URL and expected endpoints
export const dndApi = createApi({
reducerPath: "dndApi",
baseQuery: fetchBaseQuery({ baseUrl: "https://www.dnd5eapi.co/api/" }),
endpoints: (builder) => ({
getMonstersByName: builder.query({
query: (name: string) => `monsters/${name}`,
}),
}),
});
export const { useGetMonstersByNameQuery } = dndApi;
export const store = configureStore({
reducer: { [dndApi.reducerPath]: dndApi.reducer },
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(dndApi.middleware),
});
import * as React from "react";
import { useGetMonstersByNameQuery } from "./services/dnd";
export default function App() {
const { data, error, isLoading } = useGetMonstersByNameQuery("aboleth");
return (
<div className="App">
{error ? (
<>Oh no, there was an error</>
) : isLoading ? (
<>Loading...</>
) : data ? (
<>
<h3>{data.name}</h3>
<h4> {data.speed.walk} </h4>
</>
) : null}
</div>
);
}