"Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers." - javascript

I am trying to fetch the local json data and then using it inside my component.
I have mentioned the extraReducers while keeping the reducer empty. Below is the code,
import { createAsyncThunk, configureStore } from "#reduxjs/toolkit";
export const fetchData = createAsyncThunk('data/fetch', async () =>{
const response = await fetch('../src/assets/relatives.json');
const data = await response.json();
return data;
})
const dataSlice = {
name: 'data',
initialState: [],
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchData.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchData.fulfilled, (state, action) => {
state.status = 'succeeded';
state.data = action.payload;
})
.addCase(fetchData.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
},
};
const store = configureStore({
reducer:{
data: dataSlice,
},
preloadedState: {data:[]},
})
export default store;
This is my component:
import React, { useEffect } from "react";
import Card from "#mui/material/Card";
import CardActions from "#mui/material/CardActions";
import CardContent from "#mui/material/CardContent";
import Button from "#mui/material/Button";
import Typography from "#mui/material/Typography";
import TreeView from "#mui/lab/TreeView";
import { ExpandMore ,ChevronRight } from "#mui/icons-material";
import TreeItem from "#mui/lab/TreeItem";
import { useDispatch, useSelector } from "react-redux";
import { fetchData } from "../configureStore";
const FamilyFolder = () => {
const dispatch = useDispatch();
const data = useSelector((state = state.data.data));
const status = useSelector((state) => state.data.status);
useEffect(() => {
dispatch(fetchData());
}, [dispatch]);
if (status === "loading") {
return<div>Loading...</div>
}
if (status === 'failed') {
return <div>Error</div>;
}
console.log(data);
return (
<div>
<Card sx={{ minWidth: 50 }}>
<CardContent>
<Typography
sx={{ fontSize: 14, align: "center" }}
color="text.secondary"
gutterBottom
>
Family Tree
</Typography>
</CardContent>
<TreeView
aria-label="file system navigator"
defaultCollapseIcon={<ExpandMore />}
defaultExpandIcon={<ChevronRight />}
sx={{ height: 240, flexGrow: 1, maxWidth: 400, overflowY: "auto" }}
>
<TreeItem nodeId="1" label="gg">
<TreeItem nodeId="2" label="Calendar" />
</TreeItem>
<TreeItem nodeId="5" label="Documents">
<TreeItem nodeId="10" label="OSS" />
<TreeItem nodeId="6" label="MUI">
<TreeItem nodeId="8" label="index.js" />
</TreeItem>
</TreeItem>
</TreeView>
<CardActions className="parent-button-container">
<CardActions className="">
<Button variant="contained">Import JSON</Button>
<Button variant="contained">Add Family</Button>
</CardActions>
<CardActions className="">
<Button variant="contained">Export JSON</Button>
<Button variant="contained">Print Tree</Button>
</CardActions>
</CardActions>
</Card>
</div>
);
};
export default FamilyFolder;
I have added the preloadedState inside the store as initial state but that is also not working.
Should i mention anything inside reducer: {} ?. If yes, please tell what i should do

Related

Element is not rendered from redux

After clicking the "Add client" button, the state changes, but the element is not render
I started learning redux recently. I'm trying to understand the logic of the framework. Therefore, I decided to make a bank emulation. You can add money and get it. With clients the same functionality. But for some reason the added clients are not rendered
I think the problem in my App.js.
My App.js
import { useDispatch, useSelector } from "react-redux";
import "./App.css";
function App() {
const dispatch = useDispatch();
const cash = useSelector((state) => state.cash.cash);
const customers = useSelector((state) => state.customer.customers);
const addCash = (cash) => {
dispatch({ type: "ADD_CASH", payload: cash });
};
const getCash = (cash) => {
dispatch({ type: "GET_CASH", payload: cash });
};
const addCustomer = (name) => {
const customer = {
name,
id: Date.now(),
};
dispatch({ type: "ADD_CUSTOMER", payload: customer });
};
return (
<div className="App">
<div style={{ display: "flex" }}>
<button onClick={() => addCash(Number(prompt()))}>Add cash</button>
<button onClick={() => getCash(Number(prompt()))}>Get cash</button>
</div>
<div style={{ fontSize: "2rem" }}>{cash}</div>
{customers.length > 0 ? (
<div>
{customers.map((customer) => {
<div style={{ fontSize: "2rem" }}>{customer.name}</div>;
})}
</div>
) : (
<div style={{ fontSize: "2rem" }}>No clients</div>
)}
<div style={{ display: "flex" }}>
<button onClick={() => addCustomer(prompt())}>Add client</button>
{/* <button onClick={() => getCash(Number(prompt()))}>Get client</button> */}
</div>
</div>
);
}
export default App;
My store
import { createStore, combineReducers } from "redux";
import { cashReducer } from "./cashReducer";
import { customerReducer } from "./customerReducer";
import { composeWithDevTools } from "redux-devtools-extension";
const rootReducer = combineReducers({
cash: cashReducer,
customer: customerReducer,
});
export const store = createStore(rootReducer, composeWithDevTools());
**My sustomer reducer**
const defaultState = {
customers: [],
};
export const customerReducer = (state = defaultState, action) => {
switch (action.type) {
case "ADD_CUSTOMER":
return { ...state, customers: [...state.customers, action.payload] };
case "REMOVE_CUSTOMERS":
return { ...state, cash: state.cash - action.payload };
default:
return state;
}
};
My cash reducer
const defaultState = {
cash: 5,
};
export const cashReducer = (state = defaultState, action) => {
switch (action.type) {
case "ADD_CASH":
return { ...state, cash: state.cash + action.payload };
case "GET_CASH":
return { ...state, cash: state.cash - action.payload };
default:
return state;
}
};
And my index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { store } from './store';
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
reportWebVitals();
//this
{customers.map((customer) => {
<div style={{ fontSize: "2rem" }}>{customer.name}</div>;
})}
//should be this... with parenthesis instead of curly braces around the div
{customers.map((customer) => (
<div style={{ fontSize: "2rem" }}>{customer.name}</div>;
))}

how do I prevent re-render in react

Now I have created this custom hook to perform lazy loading,which takes redux slice action as input and
import { useState, useEffect, useCallback, useRef } from "react";
import { useDispatch } from "react-redux";
function useLazyFetch(fetchAction) {
const dispatch = useDispatch();
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const loadMoreRef = useRef(null);
const handleObserver = useCallback(async(entries) => {
const [target] = entries;
console.log(target.isIntersecting);
if (target.isIntersecting) {
console.log("INTERSECTING.....");
await new Promise((r) => setTimeout(r, 2000));
setPage((prev) => prev + 1);
}
}, []);
useEffect(() => {
const option = {
root: null,
rootMargin: "0px",
threshold: 1.0,
};
const observer = new IntersectionObserver(handleObserver, option);
if (loadMoreRef.current) observer.observe(loadMoreRef.current);
}, [handleObserver]);
const fetchApi = useCallback(async () => {
try {
setLoading(true);
await new Promise((r) => setTimeout(r, 2000));
dispatch(fetchAction(page))
setLoading(false);
} catch (err) {
console.error(err);
}
}, [page,fetchAction,dispatch]);
useEffect(() => {
fetchApi();
}, [fetchApi]);
return { loading, loadMoreRef };
}
export default useLazyFetch;
I am using this in my component like this, here you can see I am tracking div in the bottom using loadMoreRef from useLazyFetch, Now when I am commenting out the fetchApi(); from custom hook its working as expected, on scroll its logging INTERSECTING... in the console but the moment I try to execute the action through fetchApi() my whole app goes into loop,the div tracker with ref comes to top and it fetches the posts but after immediately that action repeats the tracker comes to top and page becomes empty & it fetches next set of posts,I can see that my list is getting appended new set of posts to state in redux dev tool instead of completely setting new state, but in UI it's rendering all posts again and again whic is causing the loop,how can I avoid this ?
import { CircularProgress, Grid, IconButton, Typography } from "#mui/material";
import { Box } from "#mui/system";
import React, { useEffect,useRef,useState } from "react";
import AssistantIcon from "#mui/icons-material/Assistant";
import Post from "../components/Post";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../redux/postSlice";
import AddPost from "../components/AddPost";
import useLazyFetch from "../hooks/useLazyFetch";
export default function Home() {
const dispatch = useDispatch();
// const api = `https://picsum.photos/v2/list`
const { status, posts } = useSelector((state) => state.post);
const {loading,loadMoreRef} = useLazyFetch(getPosts)
useEffect(() => {
dispatch(getPosts());
}, []);
return (
<Box>
<Box borderBottom="1px solid #ccc" padding="8px 20px">
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Typography variant="h6">Home</Typography>
</Grid>
<Grid item>
<IconButton>
<AssistantIcon />
</IconButton>
</Grid>
</Grid>
</Box>
<Box height="92vh" sx={{ overflowY: "scroll" }}>
<AddPost />
<Box textAlign="center" marginTop="1rem">
{status === "loading" && (
<CircularProgress size={20} color="primary" />
)}
</Box>
{status === "success" &&
posts?.map((post) => <Post key={post._id} post={post} />)}
<div style={{height:"50px",width:"100px",backgroundColor:"red"}} ref={loadMoreRef}>{loading && <p>loading...</p>}</div>
</Box>
</Box>
);
}
And here is my redux action & state update part
const initialState = {
status: "idle",
posts: []
};
export const getPosts = createAsyncThunk("post/getPosts", async (page) => {
console.log(page);
console.log("calling api ...");
const { data } = await axios.get(`/api/posts?page=${page}`);
return data;
});
export const postSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: {
[getPosts.pending]: (state, action) => {
state.status = "loading";
},
[getPosts.fulfilled]: (state, action) => {
state.status = "success";
state.posts = [...state.posts,...action.payload.response.posts] ;
},
[getPosts.rejected]: (state, action) => {
state.status = "failed";
},
}
this is the solution that is working
import { CircularProgress, Grid, IconButton, Typography } from "#mui/material";
import { Box } from "#mui/system";
import React, { useEffect,useMemo } from "react";
import AssistantIcon from "#mui/icons-material/Assistant";
import Post from "../components/Post";
import { useDispatch, useSelector } from "react-redux";
import { getPosts } from "../redux/postSlice";
import AddPost from "../components/AddPost";
import useLazyFetch from "../hooks/useLazyFetch";
export default function Home() {
const { status, posts } = useSelector((state) => state.post);
const {loading,loadMoreRef} = useLazyFetch(getPosts)
const renderedPostList = useMemo(() => (
posts.map((post) => {
return( <Post key={post._id.toString()} post={post} />)
})
), [posts])
return (
<Box>
<Box borderBottom="1px solid #ccc" padding="8px 20px">
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Typography variant="h6">Home</Typography>
</Grid>
<Grid item>
<IconButton>
<AssistantIcon />
</IconButton>
</Grid>
</Grid>
</Box>
<Box height="92vh" sx={{ overflowY: "scroll" }}>
<AddPost />
<Box textAlign="center" marginTop="1rem">
{status === "loading" && (
<CircularProgress size={20} color="primary" />
)}
</Box>
{renderedPostList}
<div style={{height:"50px",width:"100px",backgroundColor:"red"}} ref={loadMoreRef}>{loading && <p>loading...</p>}</div>
</Box>
</Box>
);
}
}
I used useMemo hook to memoize and it works as expected

Strange State / UseEffect Behaviour using NextJS Link

I am experiencing so really strange behaviour in this personal project i am doing. In short it is a recipe website, with buttons at the top that direct to different pages that ultimately query firebase and pull down a query.
Index.js File - Queries Firestore passes props to FoodCard.js to Render a list of all recipes
breakfast.js - Queries Firestore with a filter and passes same props down (with different results) to FoodCard.js
Behaviour
When i click on Breakfast JS, it brings up my list of filtered results correctly, however when i click my Next Link "Welcome to the Family Heirloom" to return to the index the first click doesnt respond, then the second click returns home, but with the filtered breakfast result concatonated with all the original results (effectively causing duplicates)Index on First Render
Breakfast filter successful
index with the now duplicate pancake result
I have messed about wondering if useEffect is not getting triggered which you may see in the code, but that didnt seem to work, so at a loss
Index.js
import { React, useEffect, useState } from 'react'
import { useRouter } from 'next/dist/client/router'
import { useTheme } from '#mui/material/styles'
import { makeStyles } from '#mui/styles'
import Modal from '#mui/material/Modal'
import FoodCard from '../src/ui/FoodCard.js'
import firebase from '../firebase/initFirebase'
import Box from '#mui/material/Box'
import Typography from '#mui/material/Typography'
const useStyles = makeStyles(theme => ({
mainContainer: {
}
}))
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'red',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
export default function CookBook() {
const router = useRouter();
const { id } = router.query;
const classes = useStyles()
const theme = useTheme()
const [loading, setLoading] = useState(true);
const [recipes, setRecipes] = useState([]);
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
useEffect(() => {
const getRecipesFromFirebase = [];
const subscriber = firebase.firestore()
.collection("recipes")
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(querySnapshot)
getRecipesFromFirebase.push({
...doc.data(), //spread operator
key: doc.id, // `id` given to us by Firebase
});
});
setRecipes(getRecipesFromFirebase);
console.log(recipes);
setLoading(false);
});
return () => subscriber();
}, [loading, router.events]); // empty dependencies array => useEffect only called once
if (loading) {
return (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Typography id="modal-modal-title" variant="h6" component="h2">
Loading Data
</Typography>
</Box>
</Modal>)
}
return (
<FoodCard recipes={recipes} />
)
}
_app.js
import * as React from 'react';
import PropTypes from 'prop-types';
import Head from 'next/head';
import { ThemeProvider } from '#mui/material/styles';
import CssBaseline from '#mui/material/CssBaseline';
import { CacheProvider } from '#emotion/react';
import theme from '../src//ui/theme';
import createEmotionCache from '../src/createEmotionCache';
import Header from '../src/ui/Header';
import Grid from '#mui/material/Grid'
import Typography from '#mui/material/Typography'
import { Link as MUILink } from '#mui/material/'
import NextLink from 'next/link'
// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();
export default function MyApp(props) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
return (
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<Header />
<Grid container justify="center" alignItems="center" direction="column" >
<Grid item>
<NextLink href="/" passHref>
<MUILink underline="none" color="secondary" variant="h1">
Welcome To the Family Heirloom
</MUILink>
</NextLink>
</Grid>
</Grid>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
MyApp.propTypes = {
Component: PropTypes.elementType.isRequired,
emotionCache: PropTypes.object,
pageProps: PropTypes.object.isRequired,
};
Breakfast.js
import { React, useEffect, useState } from 'react'
import FilterMains from '../../src/ui/FilterMains'
import { useRouter } from 'next/dist/client/router'
import FoodCard from '../../src/ui/FoodCard'
import {
getFirestore, collection, query, where, onSnapshot
} from 'firebase/firestore'
const Breakfast = () => {
const router = useRouter();
const { id } = router.query;
const [breakfastloading, setBreakfastLoading] = useState(true);
const [breakfastRecipe, setBreakfastRecipe] = useState([]);
const db = getFirestore()
const docRef = collection(db, 'recipes')
//Query
const q = query(docRef, where("category", "==", 'Breakfast'))
useEffect(() => {
const getBreakfastFromFirebase = [];
onSnapshot(q, (snapshot) => {
snapshot.docs.forEach((doc) => {
getBreakfastFromFirebase.push({ ...doc.data() })
})
setBreakfastRecipe(getBreakfastFromFirebase)
setBreakfastLoading(false)
console.log(breakfastRecipe)
})
}, [breakfastloading, router.events]);
if (breakfastloading) {
return (
<h2>Loading Data</h2>
)
}
return (
<FoodCard recipes={breakfastRecipe} />
// <FoodCard recipes={recipes} />
)
}
export default Breakfast
FoodCard.js
import React from 'react'
import Card from '#mui/material/Card'
import CardHeader from '#mui/material/CardHeader';
import CardMedia from '#mui/material/CardMedia';
import Grid from '#mui/material/Grid'
import Container from '#mui/material/Container';
import Link from 'next/link'
import CardActionArea from '#mui/material/CardActionArea';
function FoodCard(props) {
return (
<div>
<Container>
< Grid container justify="center" alignItems="center" direction="row" >
<Grid container spacing={2}>
{props.recipes.map((recipe) => (
<Link href={`/recipes/${recipe.key}`} passHref>
<Grid key={recipe.id} item xs={12} md={6}>
<Card elevation={3} sx={{ maxWidth: 400 }}>
<CardActionArea>
<CardHeader
titleTypographyProps={{ fontWeight: "Bold" }}
title={recipe.title}
subheader={recipe.description}
/>
<CardMedia
component="img"
height="194"
image="/assets/comingsoon.jpg"
alt="Mac and Cheese"
/>
</CardActionArea>
</Card>
</Grid>
</Link>
))}
</Grid>
</Grid >
</Container>
</div>
)
}
export default FoodCard
Cracked it, although i can't fully explain why. In my learning i used two different methods for interfacing with firebase. The first method on the index page was what i would call the older way. The second method i used was in all of my other files, specific to interfacing with Version 9 of Firebase.
from this:
const subscriber = firebase.firestore()
.collection("recipes")
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(querySnapshot)
getRecipesFromFirebase.push({
...doc.data(), //spread operator
key: doc.id, // `id` given to us by Firebase
});
});
to this:
let getRecipesFromFirebase = [];
getDocs(colRef)
.then((snapshot) => {
let getRecipesFromFirebase = [];
snapshot.docs.forEach((doc) => {
getRecipesFromFirebase.push({ ...doc.data(), key: doc.id })
})
setRecipes(getRecipesFromFirebase);
console.log(recipes);
setLoading(false);
})
.catch(error => {
console.log(error.message)
})
Would love somebody more knowledgable (3 months into React) than myself to tell me as to why though.

using useparams and array.find to display detailed data

I am making a small blog application using react js. I have a context api for the user inputs, so that the data can be used globally across components (InputContext.js). Using react router, the user is able to view a list of all blog entries (AllBlogs.js) and view each one of them in detail (BlogDetail.js). What I am trying to achieve is, allow the user to get a detailed view of an individual blog post component from the AllBlogs.js page. All blogs have an "id" property, which is used to query the url and using the array.find method, it is supposed to show a detailed view of the blog with the matching id. The problem is "findBlogs" in BlogDetails that is being passed as a prop to display the detailed individual blog data always only returns the most recent user input value, therefore all blogs show the exact same information. I am unsure as to why this is happening, any guidance towards the right direction is greatly appreciated.
InputContext.js
import React, { useState, createContext, useMemo } from 'react'
//create context
export const InputContext = createContext();
const InputContextProvider = (props) => {
const [blogPost, setBlogPost] = useState({
id: '',
title: '',
author: '',
text: ''
});
//create an array to push all the blogPosts
const [allBlogPosts, setAllBlogPosts] = useState([]);
console.log(allBlogPosts)
//put value inside useMemo so that the component only rerenders when there is change in the value
const value = useMemo(() => ({ blogPost, setBlogPost, allBlogPosts, setAllBlogPosts }), [blogPost, allBlogPosts])
return (
<InputContext.Provider value={value}>
{props.children}
</InputContext.Provider>
)
}
export default InputContextProvider;
WriteBlogPost.js
import React, { useState, useContext, Fragment } from 'react'
import { useHistory } from 'react-router-dom'
import { InputContext } from '../Contexts/InputContext'
import { TextareaAutosize } from '#material-ui/core'
import { v4 as uuidv4 } from 'uuid';
import { Box, TextField, Button, makeStyles } from '#material-ui/core'
const useStyles = makeStyles({
root: {
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center'
}
})
export const WriteBlogPost = () => {
const classes = useStyles();
const [blog, setBlog] = useState({
id: '',
title: '',
author: '',
text: ''
});
const history = useHistory();
const { setBlogPost } = useContext(InputContext);
const { allBlogPosts, setAllBlogPosts } = useContext(InputContext)
const handleBlogPost = () => {
setBlogPost(blog);
setAllBlogPosts([...allBlogPosts, blog]);
history.push("/blogs")
console.log({ blog })
console.log({ allBlogPosts })
}
const handleChange = (e) => {
const value = e.target.value
setBlog({
...blog,
id: uuidv4(),
[e.target.name]: value
})
}
return (
<Fragment>
<Box className={classes.root}>
<div>
<TextField id="standard-basic" onChange={handleChange} value={blog.title} name="title" label="Title" />
</div>
<div>
<TextField id="standard-basic" onChange={handleChange} value={blog.author} name="author" label="Author" />
</div>
<div>
<TextareaAutosize aria-label="minimum height" minRows={20} style={{ width: '70%' }} placeholder="Your blog post"
onChange={handleChange}
value={blog.text}
name="text" />
</div>
<div>
<Button variant="contained" color="primary" onClick={handleBlogPost}>
Submit</Button>
</div>
</Box>
</Fragment>
)
}
AllBlogs.js
import React, { useContext } from 'react'
import { InputContext } from '../Contexts/InputContext'
import { Card, CardContent, Typography } from '#material-ui/core'
import { makeStyles } from '#material-ui/core'
import { Link } from 'react-router-dom'
const useStyles = makeStyles({
root: {
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
textAlign: 'center',
},
text: {
textAlign: 'center'
}
})
export const AllBlogs = () => {
const classes = useStyles();
const { allBlogPosts, blogPost } = useContext(InputContext)
console.log(allBlogPosts)
return (
<div>
<Typography color="textPrimary" variant="h3" className={classes.text}>All blogs</Typography>
{allBlogPosts.map((post, i) =>
<Card variant="outlined" key={i} className={classes.root}>
<CardContent>
<Typography color="textPrimary" variant="h5">
{post.title}
</Typography>
<Typography color="textPrimary" variant="h6">
{post.author}
</Typography>
<Typography color="textPrimary" variant="body2" component="p">
{post.text}
</Typography>
<Link to={`/blogs/${blogPost.id}`}>
Read blog
</Link>
</CardContent>
</Card>
)}
</div>
)
}
BlogDetail.js
import React, { useContext } from 'react'
import { useParams, Route } from 'react-router'
import { SingleBlog } from './SingleBlog';
import { InputContext } from '../Contexts/InputContext';
export const BlogDetail = () => {
const params = useParams();
console.log(params.blogId)
const { allBlogPosts } = useContext(InputContext)
const findBlog = allBlogPosts.find((post) => post.id === params.blogId)
console.log(findBlog)
if (!findBlog) {
return <p>No blogs found.</p>
}
return (
<div>
<h1>Blog details</h1>
<SingleBlog post={findBlog} />
</div>
)
}
Issue
Ah, I see what is happening... had to dig back through your edits to when you included your context code.
In your provider you for some reason store an array of blogs (this part makes sense), but then you also store the last blog that was edited.
const InputContextProvider = (props) => {
const [blogPost, setBlogPost] = useState({
id: '',
title: '',
author: '',
text: ''
});
//create an array to push all the blogPosts
const [allBlogPosts, setAllBlogPosts] = useState([]);
//put value inside useMemo so that the component only rerenders when there is change in the value
const value = useMemo(() => ({
blogPost, // <-- last blog edited
setBlogPost,
allBlogPosts,
setAllBlogPosts
}), [blogPost, allBlogPosts])
return (
<InputContext.Provider value={value}>
{props.children}
</InputContext.Provider>
)
}
export const WriteBlogPost = () => {
...
const [blog, setBlog] = useState({
id: '',
title: '',
author: '',
text: ''
});
...
const { setBlogPost } = useContext(InputContext);
const { allBlogPosts, setAllBlogPosts } = useContext(InputContext)
const handleBlogPost = () => {
setBlogPost(blog); // <-- saves last blog edited/added
setAllBlogPosts([...allBlogPosts, blog]);
history.push("/blogs");
}
const handleChange = (e) => {
const value = e.target.value
setBlog({
...blog,
id: uuidv4(),
[e.target.name]: value
})
}
return (
...
)
}
When you are mapping the blog posts you form incorrect links.
export const AllBlogs = () => {
const classes = useStyles();
const {
allBlogPosts,
blogPost // <-- last blog updated
} = useContext(InputContext);
return (
<div>
...
{allBlogPosts.map((post, i) =>
<Card variant="outlined" key={i} className={classes.root}>
<CardContent>
...
<Link to={`/blogs/${blogPost.id}`}> // <-- link last blog updated id
Read blog
</Link>
</CardContent>
</Card>
)}
</div>
)
}
Solution
Use the current blog post's id when mapping to form the link correctly.
export const AllBlogs = () => {
const classes = useStyles();
const { allBlogPosts } = useContext(InputContext);
return (
<div>
...
{allBlogPosts.map((post, i) =>
<Card variant="outlined" key={post.id} className={classes.root}>
<CardContent>
...
<Link to={`/blogs/${post.id}`}> // <-- link current post id
Read blog
</Link>
</CardContent>
</Card>
)}
</div>
)
}

this.state.foodReducer is undefined

FoodList.js
import React, { Component } from "react";
import { View, Text, FlatList } from "react-native";
import { Content, List, ListItem } from "native-base";
import { useSelector, useDispatch } from "react-redux";
import { deleteFood } from "../../redux/actions/food";
const FoodList = () => {
const dispatch = useDispatch();
const deleteCurrent = (key) => dispatch(deleteFood(key));
const foods = useSelector((state) => state.foodReducer.FoodList); <------------
return (
<FlatList
data={foods}
keyExtractor={(item, index) => item.key.toString()}
renderItem={(data) => <ListItem title={data.item.name} />}
/>
);
};
export default FoodList;
FoodCreate.js
import { useDispatch } from "react-redux";
import { addFood } from "../../redux/actions/food";
const FoodCreate = ({ navigation: { goBack } }) => {
const [food, setFood] = useState("");
const dispatch = useDispatch();
const submitFood = (food) => dispatch(addFodd(food));
return (
<Container>
<Header>
<Left>
<Button transparent>
<Icon
name="arrow-back"
onPress={() => this.props.navigation.goBack()}
style={{ fontSize: 25, color: "red" }}
/>
</Button>
</Left>
<Body>
<Title>Add Food</Title>
</Body>
<Right>
<Button transparent>
<Icon
name="checkmark"
style={{ fontSize: 25, color: "red" }}
onPress={() => {
submitFood(food);
setFood("");
}}
/>
</Button>
</Right>
</Header>
<View style={{ alignItems: "center", top: hp("3%") }}>
<TextInput
placeholder="Food Name"
placeholderTextColor="white"
style={styles.inptFood}
value={food}
onChangeText={(food) => setFood(food)}
/>
</View>
actions/food.js
import { ADD_FOOD, DELETE_FOOD } from "./types";
export const addFood = (food) => ({
type: ADD_FOOD,
data: food,
});
export const deleteFood = (key) => ({
type: DELETE_FOOD,
key: key,
});
store/store.js
import { createStore, combineReducers } from "redux";
import foodReducer from "../reducers/foodReducer";
const rootReducer = combineReducers({
foodReducer: foodReducer,
});
const configureStore = () => createStore(rootReducer);
export default configureStore;
actions/types.js
export const ADD_FOOD = "ADD_FOOD";
export const DELETE_FOOD = "DELETE_FOOD";
reducers/foodReducer.js
import { ADD_FOOD, DELETE_FOOD } from "../actions/types";
const initialState = {
FoodList: [],
};
const foodReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_FOOD:
return {
...state,
FoodList: state.FoodList.concat({
key: Math.random(),
name: action.data,
}),
};
case DELETE_FOOD:
return {
...state,
FoodList: state.FoodList.filter((item) => item.key != action.key),
};
default:
return state;
}
};
export default foodReducer;
App.js
import FoodListScreen from "./components/Main/FoodList.js";
import configureStore from "./redux/store/store";
const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();
const store =
(createStore(rootReducer, applyMiddleware(thunk)), configureStore());
return (
<Provider store={store}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="FoodCreate"
component={FoodCreateScreen}
options={{ headerShown: false }}
/>
<Stack.Screen
name="FoodList"
component={FoodListScreen}
options={{ headerShown: false }}
/>
</Stack.Navigator>
</NavigationContainer>
</Provider>
Hey everyone, I'm new to redux and I'm trying to use it my Diet App, the user creates a Food item in FoodCreate.js and it will be listed in FoodList.js, at the moment when I run the code it gives me back the following error: state.foodReducer.FoodList is undefined, I put an arrow next to the line of code that gives me the error, I used this approach by following the following YouTube tutorial: https://www.youtube.com/watch?v=jTJ6zo5GO7E, thank in advance for your help.
ok maybe i found
//In your store/store.js
import { createStore, combineReducers,applyMiddleware } from "redux";
import thunk from 'redux-thunk'
import foodReducer from "../reducers/foodReducer";
const configureStore = () => {
const store = createStore(
combineReducers({
foodReducer: foodReducer,
}),
applyMiddleware(thunk)
);
return store;
}
export default configureStore;
//In your App.js
...
import configureStore from "./redux/store/store";
const store = ConfigureStore();
return(
<Provider store={store}>
...
</Provider>
)

Categories