I am using context and reducer API for state management and logic.
The below code returns context provider, which is called in App.js.
Why isn't the component rendering?
SingerState.js
import React, { useReducer } from "react";
import SingerContext from "./singerContext";
import SingerReducer from "./singerReducer";
import { GET_SINGER, SET_LOADING } from "../types";
const SingerState = (props) => {
console.log('foo')
const initialState = {
name: null,
songs: null,
loading: false,
};
const [state, dispatch] = useReducer(SingerReducer, initialState);
const getSinger = () => {
dispatch({
type: GET_SINGER,
payload: {
name: "jb",
songs: ["song1", "song2"],
},
});
};
const setLoading = () => {
dispatch({
type: SET_LOADING,
});
};
return (
<SingerContext.Provider
value={{
name: state.name,
songs: state.songs,
loading: state.loading,
getSinger,
setLoading,
}}
>
{props.childer}
</SingerContext.Provider>
);
};
export default SingerState;
The entire code is in here: https://codesandbox.io/s/heuristic-shockley-9u2xb?file=/src/App.js:0-411
App.js
import "./App.css";
// import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import SingerState from "./context/singer/singerState";
import Navbar from "./components/Navbar";
import Singer from "./components/singer/Singer";
export default function App() {
return (
<div className='App'>
<Navbar />
<SingerState>
<Singer />
</SingerState>
</div>
);
}
singerReducer.js
import { GET_SINGER, SET_LOADING } from "../types";
const reducer = (state, action) => {
switch (action.types) {
case GET_SINGER:
return {
...state,
name: action.payload.name,
song: action.payload.song,
loading: false,
};
case SET_LOADING:
return {
...state,
loading: true,
};
default:
return state;
}
};
export default reducer;
singerContext.js
import { createContext } from "react";
const singerContext = createContext();
export default singerContext;
It should be {props.children} not {props.childer} in SingerState.js. I made this correction inside your Sandbox and it is working. To make sure, I changed initialState to this :
const initialState = {
name: "It's me",
songs: null,
loading: false,
};
You have 2 typos in your code:
1-
It should be {props.children} not {props.childer} in SingerState.js. (Thanks to yousoumar)
And in SingerReducer.js.
Instead of :
switch (action.types) {
case GET_SINGER:
use action.type because types is not defined
Be carefully with typos!!!
Related
I've been getting an undefined in home.js when I try to console.log(products)
I tried to use postman, and it can successfully get the data from the database, also inside apicalls.js, when I try to console.log(res.data).
Am I missing something? I tried to copy in the tutorial, but it didn't work for me.
apicalls.js
import axios from 'axios'
import {
getProducstStart,
getProductsFailure,
getProductsSuccess,
} from './ProductAction'
export const getProducts = async (dispatch) => {
dispatch(getProducstStart())
try {
const res = await axios.get('/product')
dispatch(getProductsSuccess(res.data))
} catch (error) {
dispatch(getProductsFailure())
}
}
home.js
import React, { useContext, useEffect } from 'react'
import { getProducts } from '../../context/ApiCalls'
import { ProductContext } from '../../context/ProductContext'
const Home = () => {
const { products, dispatch } = useContext(ProductContext)
useEffect(() => {
getProducts(dispatch)
}, [dispatch])
console.log(products)
return (
<div className="home">
</div>
)
}
ProducetReducer.js
const ProductReducer = (state, action) => {
switch (action.type) {
case 'GET_PRODUCT_START':
return {
products: [],
isFetching: true,
error: false,
}
case 'GET_PRODUCT_SUCCESS':
return {
products: action.payload,
isFetching: false,
error: false,
}
case 'GET_PRODUCT_FAILURE':
return {
products: [],
isFetching: false,
error: true,
}
default:
return { ...state }
}
}
export default ProductReducer
ProductContext.js
import { createContext, useReducer } from 'react'
import ProductReducer from './ProductReducer'
const INITIAL_STATE = {
products: [],
isFetching: false,
error: false,
}
export const ProductContext = createContext(INITIAL_STATE)
export const ProductContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(ProductReducer, INITIAL_STATE)
return (
<ProductContext.Provider
value={{
products: state.products,
isFetching: state.isFetching,
error: state.error,
dispatch,
}}
>
{children}
</ProductContext.Provider>
)
}
index.js
import { createRoot } from 'react-dom/client'
import App from './App'
import { ProductContextProvider } from './context/ProductContext'
const container = document.getElementById('root')
const root = createRoot(container)
root.render(
<ProductContextProvider>
<App />
</ProductContextProvider>
)
I am learning react-redux, so now I am trying to create react-redux crud app, here is ny solution
Here is repo : repo demo
The button
<span className="delete_info" onClick={() => deleteComment(comment.id) }>Delete</span>
The action creator to delete element
export const removeComment = id =>{
return{
type: ActionTypes.DELETE_COMMENTS,
payload:id
}
}
// delete comments
export const deleteComment = id =>{
console.log('ids', id);
return dispatch =>{
dispatch(fetchCommentsRequest())
axios.delete(`/api/v1/todo/${id}`)
.then(response =>{
console.log('yeees mom', response.data)
dispatch(removeComment(id))
})
.catch(error =>{
const erroMsg =error.message;
console.log('eeeror', erroMsg)
dispatch(fetchCommentsFailure(erroMsg))
})
}
}
Here is my reducer
import * as ActionTypes from '../action-types'
const initialState ={
data:[],
error:'',
comments:[],
loading:false,
editing:false
}
const reducer = (state= initialState, action) => {
switch (action.type) {
case ActionTypes.FETCH_COMMENTS_REQUEST:
return{
...state,
loading: true,
}
case ActionTypes.FETCH_COMMENTS_SUCCESS:
return{
...state,
loading:false,
comments:action.payload,
error:''
}
case ActionTypes.FETCH_COMMENTS_FAILURE:
return{
...state,
loading:false,
error:action.payload
}
case ActionTypes.ADD_COMMENTS:
return{
...state,
comments:state.comments.concat(action.payload)
}
case ActionTypes.DELETE_COMMENTS:
return{
...state,
comments: state.comments.filter(comment =>comment.id !==action.payload)
}
case ActionTypes.EDIT_COMMENTS:
return{
...state,
comments: state.comments.map(comment =>comment.id === action.payload?{
...comment,
editing:!editing
}:comment)
}
default: // need this for default case
return state
}
}
export default reducer
Now when I click delete, I see on the console the ID from action creators, but the element is not removed
and no errors, what is wrong here?
There's a few knots; I made this simplified sandbox (mocking an api call) of how it should work:
https://codesandbox.io/s/wonderful-minsky-99xi4?file=/src/App.js:0-799
index
import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunkMiddleware from "redux-thunk";
import rootReducer from "./rootReducer";
import App from "./App";
const store = createStore(rootReducer, applyMiddleware(thunkMiddleware));
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
App
import React from "react";
import "./styles.css";
import { connect } from "react-redux";
import deleteRequest from "./deleteRequest";
const mapStateToProps = state => {
return {
comments: state.comments
};
};
const mapDispatchToProps = {
deleteRequest: deleteRequest
};
let App = ({ comments, deleteRequest }) => {
const makeDeleteRequest = id => {
deleteRequest(id);
};
return (
<div className="App">
{comments.map(comment => {
return (
<div key={comment.id}>
<p>{comment.text}</p>
<button onClick={() => makeDeleteRequest(comment.id)}>
Delete
</button>
</div>
);
})}
</div>
);
};
App = connect(
mapStateToProps,
mapDispatchToProps
)(App);
export default App;
reducer
const initialState = {
data: [],
error: "",
comments: [{ id: 1, text: "test1" }, { id: 2, text: "test2" }],
loading: false,
editing: false
};
function rootReducer(state = initialState, action) {
switch (action.type) {
case "DELETE_COMMENT":
return {
...state,
comments: state.comments.filter(comment => comment.id !== action.id)
};
default:
return state;
}
}
export default rootReducer;
async action
import deleteComment from "./deleteComment";
const mockAPI = new Promise(function(resolve, reject) {
setTimeout(() => resolve("deleted"), 2000);
});
const deleteRequest = id => {
return dispatch => {
const makeDeleteRequest = async () => {
await mockAPI;
dispatch(deleteComment(id));
};
makeDeleteRequest();
};
};
export default deleteRequest;
delete comment action
export default function deleteComment(id) {
return { type: "DELETE_COMMENT", id };
}
It looks like you're not actually dispatching the action, you're just returning an async action creator (deleteComment function). In order your code to work, you need to first add redux-thunk middleware to your redux store (so that you can use async action creators) and then, in your component, when you're calling deleteComponent, you have to wrap the call using redux dispatch.
If you're using a function component, you can add useDispatch hook and have something like:
import {useDispatch} from "react-redux";
// ...
function MyComponent() {
const dispatch = useDispatch();
// ...
return <span className="delete_info" onClick={() => dispatch(deleteComment(comment.id))}>Delete</span>
}
or you can just use the connect function to create a HOC and pass the dispatch function from the provider's context:
const ConnectedComponent = connect(undefined, dispatch => ({dispatch}))(MyComponent);
function MyComponent({dispatch}) {
return <span className="delete_info" onClick={() => dispatch(deleteComment(comment.id))}>Delete</span>;
}
I don't know how to load the data of the fetchLatestAnime action in the react app.js file.
My mission is to show the endpoint data that I am doing fetch.
I have already implemented the part of the reducers and action, which you can see in the part below. The only thing I need is to learn how to display the data.
App.js
import React from 'react';
import './App.css';
function App() {
return (
<div className="App">
</div>
);
}
export default App;
actions/types.js
export const FETCHING_ANIME_REQUEST = 'FETCHING_ANIME_REQUEST';
export const FETCHING_ANIME_SUCCESS = 'FETCHING_ANIME_SUCCESS';
export const FETCHING_ANIME_FAILURE = 'FETCHING_ANIME_FAILURE';
actions/animesActions.js
import{
FETCHING_ANIME_FAILURE,
FETCHING_ANIME_REQUEST,
FETCHING_ANIME_SUCCESS
} from './types';
import axios from 'axios';
export const fetchingAnimeRequest = () => ({
type: FETCHING_ANIME_REQUEST
});
export const fetchingAnimeSuccess = (json) => ({
type: FETCHING_ANIME_SUCCESS,
payload: json
});
export const fetchingAnimeFailure = (error) => ({
type: FETCHING_ANIME_FAILURE,
payload: error
});
export const fetchLatestAnime = () =>{
return async dispatch =>{
dispatch(fetchingAnimeRequest());
try{
let res = await axios.get('https://animeflv.chrismichael.now.sh/api/v1/latestAnimeAdded');
let json = await res.data;
dispatch(fetchingAnimeSuccess(json));
}catch(error){
dispatch(fetchingAnimeFailure(error));
}
};
};
reducers/latestAnimeReducers.js
import {
FETCHING_ANIME_FAILURE,
FETCHING_ANIME_REQUEST,
FETCHING_ANIME_SUCCESS
} from '../actions/types';
const initialState = {
isFetching: false,
errorMessage: '',
latestAnime: []
};
const latestAnimeReducer = (state = initialState , action) =>{
switch (action.type){
case FETCHING_ANIME_REQUEST:
return{
...state,
isFetching: true,
}
case FETCHING_ANIME_FAILURE:
return{
...state,
isFetching: false,
errorMessage: action.payload
}
case FETCHING_ANIME_SUCCESS:
return{
...state,
isFetching: false,
latestAnime: action.payload
}
default:
return state;
}
};
export default latestAnimeReducer;
reducers/index.js
import latestAnimeReducers from './latestAnimeReducers'
import {combineReducers} from 'redux';
const reducers = combineReducers({
latestAnimeReducers
});
export default reducers;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import resolvers from './redux/reducers/index';
import {createStore , applyMiddleware} from 'redux';
import {Provider} from 'react-redux';
import thunk from 'redux-thunk';
const REDUX_DEV_TOOLS = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(resolvers , REDUX_DEV_TOOLS)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
Ideally, this is how your app.js should look like. I created a working codesandbox for you here. Your initial latestAnime state was an empty array but the action payload you set to it is an object, so remember to pass payload.anime like i have done in the sandbox.
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { fetchLatestAnime } from "./redux/actions/animesActions";
const App = props => {
const { fetchLatestAnime, isFetching, latestAnime, errorMessage } = props;
useEffect(() => {
fetchLatestAnime();
}, [fetchLatestAnime]);
console.log(props);
if (isFetching) {
return <p>Loading</p>;
}
if (!isFetching && latestAnime.length === 0) {
return <p>No animes to show</p>;
}
if (!isFetching && errorMessage.length > 0) {
return <p>{errorMessage}</p>;
}
return (
<div>
{latestAnime.map((anime, index) => {
return <p key={index}>{anime.title}</p>;
})}
</div>
);
};
const mapState = state => {
return {
isFetching: state.latestAnimeReducers.isFetching,
latestAnime: state.latestAnimeReducers.latestAnime,
errorMessage: state.latestAnimeReducers.errorMessage
};
};
const mapDispatch = dispatch => {
return {
fetchLatestAnime: () => dispatch(fetchLatestAnime())
};
};
export default connect(
mapState,
mapDispatch
)(App);
After one of the Redux tutorials decided to implement that facny action->reducer->store->view chain for simple app with only login part.
Seems like all setted up but when I run my app - in mapStateToProps(currentState) no any sign of the any custom state fields which I expected to see! (default state from reducer). But the action function is fine, as you can see on the screenshot
I can't see whats wrong here so, decided to ask it.
So here is the code
So, first of all - store.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState, applyMiddleware(thunk));
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
then login reducer
const initialState = {
user: {
name: '',
password: ''
},
fetching: false
}
export default function login(state = initialState, action) {
switch (action.type) {
case LOGIN_REQUEST: {
return { ...state, fetching: true }
}
case LOGIN_SUCCESS: {
return { ...state, user: action.data, fetching: false }
}
case LOGIN_FAIL: {
return { ...state, user: -1, fetching: false }
}
default: {
return state;
}
}
}
and the root (reducers/index.js):
import login from './login/login';
import { combineReducers } from 'redux'
export default combineReducers({
login
});
the action
import {
LOGIN_REQUEST,
LOGIN_SUCCESS,
LOGIN_FAIL
} from '../../constants/login.js';
export function onLoginAttempt(userData) {
return (dispatch) => {
dispatch({
type: LOGIN_REQUEST,
user: userData
})
tryLogin(userData);
}
};
function tryLogin(userData) {
let url = 'SignIn/Login ';
return (dispatch) => {
axios.post(url, userData)
.then((response) => dispatch({
type: LOGIN_SUCCESS,
data: response.data
})).error((response) => dispatch({
type: LOGIN_FAIL,
error: response.error
}))
}
};
So here is entrance point:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './containers/app.js';
import configureStore from './store/configureStore';
let store = createStore(configureStore);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("content")
);
and here is the app.js (Login is just sompe custom div with two fields nothing special)
import React, { Component } from 'react';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux';
import Login from '../components/login/Login';
import * as pageActions from '../actions/login/login'
class App extends Component {
render() {
const { user, fetching } = this.props;
const { onLoginAttempt } = this.props.pageActions;
return <div>
<Login name={user.name} password={user.password} fetching={fetching} onLoginAttempt={onLoginAttempt} />
</div>
}
}
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
I see that you have state which looks like:
reduxState = {
login: {
user: {
name: '',
password: ''
},
fetching: false
}
}
but then you try to access properties that don't exist.
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
I think you need to:
function mapStateToProps(currentState) {
return {
user: currentState.login.user,
fetching: currentState.login.fetching
}
}
You have this line let store = createStore(configureStore); on your entry point. However, inside configureStore, you have a call to createStore()
Basically you're calling something like createStore(createStore(reducers)). That's probably the cause of the problem.
You should probably call it like
let store = configureStore( /* pass the initial state */ )
I'm developing a react-redux app and I can get access to the reducers via routes. Now I'm facing the trouble of getting access to a specific reducer without using routes.
Here is my reducers.js:
const initialState = {
loading: false,
topics: []
};
export default createReducer(initialState, {
[LOADING_DATA]: (state, action) => {
return Object.assign({}, state, {
loading: action.loading
});
}
});
This is my actions.js:
export function loading (loading) {
return {
type: LOADING_DATA,
payload: {loading}
};
}
And this is what I have on my component:
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux';
import * as moduleActionCreators from '...';
import * as actionCreators from '...';
class MyComponent extends Component {
...
render () {
return (<div>
...
</div>;
}
}
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(Object.assign({}, moduleActionCreators, actionCreators), dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
Normally in the mapStateToProps I reference the reducer variables as loading: state['my_reference_to_reducer'].loading but I can't figure it out how to tell the component to reference my reducers.js in order to get loading as props.
I would appreciate a light on this.
You need to set up the state in mapStateToProps function in order to access it:
const mapStateToProps = (state) => {
return {
loading: state.loading
}
}
Then you should be able to use it as this.props.loading in MyComponent.
Your reducer can look like this:
export default function reducer(state = {}, action) {
switch(action.type) {
case 'LOADING_DATA':
return Object.assign({}, state, {
...state,
loading: action.payload.loading
})
I recommend you to use redux ducks pattern as it keeps action creators and reducers at the same file, saves you time and makes it easier to read and use. For example:
loading.js
// Actions
const LOADING_DATA = 'LOADING_DATA'
// Action Creators
export const loadingData = (data) => {
return {
type: LOADING_DATA,
payload: {
loading: data
}
}
}
// Reducer
export default function reducer(state = {
loading: 'DATA zeroed'
}, action) {
switch(action.type) {
case 'LOADING_DATA':
return Object.assign({}, state, {
...state,
loading: action.payload.loading
})
default:
return state
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import MyComponent from './MyComponent';
import configureStore from './configureStore'
const store = configureStore()
ReactDOM.render(
<MyComponent store={store}/>,
document.getElementById('root')
);
configureStore.js
import { createStore } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import loadingData from './loading'
const configureStore = () => {
return createStore(loadingData, composeWithDevTools())
}
export default configureStore
MyComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { loadingData } from './loading';
class MyComponent extends Component {
constructor(props){
super(props)
this.onLoadingData = this.onLoadingData.bind(this)
}
componentDidMount() {
this.props.loadingData('no more undefined')
}
onLoadingData() {
this.props.loadingData('DATA')
}
render() {
console.log(this.props.loading)
return (
<div>
<h2>MyComponent</h2>
<button onClick={this.onLoadingData}>Load Data</button>
<p>{this.props.loading}</p>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
loading: state.loading
}
}
const mapDispatchToProps = {
loadingData
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent)